package com.hytech.lifegame;

import junit.framework.Assert;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.view.MotionEvent;

import com.hytech.utilities.GameGridView;
import com.hytech.utilities.GridLocation;

public class LifeView extends GameGridView {

	private final int MAX_RECORDED_LOCATIONS = 3;
	private GridLocation mLastLocations[] = new GridLocation[MAX_RECORDED_LOCATIONS];
	private int mTotalRecordedLocations = 0;

	private float MIN_TRACKBALL_MOVEMENT = .1f;
	private CycleHandler mCycleHandler = new CycleHandler();

	public static final String PREF_GRID_WRAP = "Grid_Wrap";
	public static final String PREF_LOCK_SCREEN = "Lock_Screen";
	public static final String PREF_DELAY_TIME = "Pref_Delay";
	public static final String PREF_GRID_SIZE = "Pref_Grid_Size";

	// this doesn't actually control the simulation. This only let's the game
	// know if it's already running.
	private boolean mSimulationRunning = false;

	private boolean mGridWrap = false;

	// This is a selected grid.
	private int mSelectedGridX = -1;
	private int mSelectedGridY = -1;

	protected Bitmap mSelectedGrid;

	class CycleHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			LifeView.this.runCycle();
		}

		public void sleep(long delayMillis) {
			this.removeMessages(0);
			sendMessageDelayed(obtainMessage(0), delayMillis);
		}
	};

	public LifeView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		gridSizeRead();

	}

	public LifeView(Context context, AttributeSet attrs) {
		super(context, attrs);

		gridSizeRead();

	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int action = event.getAction();

		// on a mouse down or mouse move event
		if (action == MotionEvent.ACTION_DOWN
				|| action == MotionEvent.ACTION_MOVE) {

			int x = (int) event.getX();
			int y = (int) event.getY();

			GridLocation targetedlocation = findGridLocation(x, y);

			if (!isScreenLocked() && isLocationValid(targetedlocation)
					&& isLocationUsed(targetedlocation) == false) {

				markLocationUsed(targetedlocation);

				toggleGridIndex(targetedlocation);

				this.invalidate();

			}

			return true;

		} else if (action == MotionEvent.ACTION_UP) {

			// since we stopped clicking we can now forget the locations we've
			// been using.
			clearLocationsUsed();
			return true;

		}
		return super.onTouchEvent(event);
	}

	/*
	 * runCycle: Run a frame cycle on the life game.
	 */
	public void runCycle() {
		int[][] oldGridIndex = mGridIndexs;
		mGridIndexs = new int[mXGridCount][mYGridCount];
		for (int xIndex = 0; xIndex < mXGridCount; xIndex++) {
			for (int yIndex = 0; yIndex < mYGridCount; yIndex++) {
				mGridIndexs[xIndex][yIndex] = checkSpace(xIndex, yIndex,
						oldGridIndex);
			}
		}
		this.invalidate();

		Context currentContext = getContext();
		SharedPreferences sp = PreferenceManager
				.getDefaultSharedPreferences(currentContext);
		String strUpdateTime = sp.getString(PREF_DELAY_TIME, "1000");
		int iUpdateTime = Integer.parseInt(strUpdateTime.trim());
		mCycleHandler.sleep(iUpdateTime);
	}

	private int checkSpace(int x, int y, int[][] oldGridIndex) {

		/*
		 * First let's check the preferences and grab what ever we will need.
		 */

		Context currentContext = getContext();
		SharedPreferences sp = PreferenceManager
				.getDefaultSharedPreferences(currentContext);
		mGridWrap = sp.getBoolean(PREF_GRID_WRAP, false);

		/*
		 * the way the game works is we need a count of all the neighboring
		 * spaces, so we'll cycle through all 9 spaces.
		 * 
		 * 0 1 2 3 4 5 6 7 8
		 */

		int neighborCount = 0;
		for (int space_index = 0; space_index < 9; space_index++) {
			// skip this loop if it's the middle.
			if (space_index == 4) {
				continue;
			}

			// find out the new x value.
			int target_x = x + (space_index % 3) - 1;
			// is the number too low?
			if (target_x < 0) {

				if (mGridWrap) {
					target_x = mXGridCount - 1;
				} else {
					continue;
				}

				// is the number too high?
			} else if (target_x >= mXGridCount) {

				if (mGridWrap) {
					target_x = 0;
				} else {
					continue;
				}

			}

			int target_y = y + (space_index / 3) - 1;
			if (target_y < 0) {
				if (mGridWrap) {
					target_y = mYGridCount - 1;
				} else {
					continue;
				}
			} else if (target_y >= mYGridCount) {
				if (mGridWrap) {
					target_y = 0;
				} else {
					continue;
				}
			}

			if (oldGridIndex[target_x][target_y] != 0) {
				neighborCount++;
			}
		}

		// if the old space was alive, and there's two neighbors.. it's alive.
		if (oldGridIndex[x][y] != 0 && neighborCount == 2) {
			return 1;
		} else if (neighborCount == 3) {
			return 1;
		}

		return 0;
	}

	/*
	 * isLocationUsed: This finds out if this location has already been used.
	 */
	private boolean isLocationValid(GridLocation targetLocation) {
		if (targetLocation.x >= 0 && targetLocation.x < mXGridCount
				&& targetLocation.y >= 0 && targetLocation.y < mYGridCount) {
			return true;
		}

		return false;

	}

	/*
	 * isLocationUsed: This finds out if this location has already been used.
	 */
	private boolean isLocationUsed(GridLocation targetLocation) {

		for (int locationIndex = 0; locationIndex < mTotalRecordedLocations; locationIndex++) {
			Assert.assertNotNull(mLastLocations[locationIndex]);
			if (mLastLocations[locationIndex].x == targetLocation.x
					&& mLastLocations[locationIndex].y == targetLocation.y) {
				return true;
			}
		}

		return false;
	}

	/*
	 * markLocationUsed: adds the location to the list of locations used
	 * recently so we don't get double hits
	 */
	private void markLocationUsed(GridLocation targetLocation) {

		// have we not recorded the maximum number of locations?
		if (mTotalRecordedLocations < MAX_RECORDED_LOCATIONS) {
			// add another location to the list.
			mTotalRecordedLocations++;
		} else {

			// if we are already at max, we should slide these down.
			for (int locationIndex = 1; locationIndex < mTotalRecordedLocations - 1; locationIndex++) {
				mLastLocations[locationIndex] = mLastLocations[locationIndex + 1];
			}

		}

		// copy over the last entry.
		mLastLocations[mTotalRecordedLocations - 1] = targetLocation;

	}

	/**
	 * Is the Screen Locked from entry.
	 * 
	 * @return true if the screen should not allow editing.
	 */
	public boolean isScreenLocked() {
		// quick check to see if the simulation is not running. If it's not,
		// there's no way it'll be locked.
		if (mSimulationRunning == false) {
			return false;
		}

		Context currentContext = getContext();
		SharedPreferences sp = PreferenceManager
				.getDefaultSharedPreferences(currentContext);
		return sp.getBoolean(PREF_LOCK_SCREEN, false);

	}

	/*
	 * clearLocationsUsed: clear the list of the locations used.
	 */
	public void clearLocationsUsed() {
		mTotalRecordedLocations = 0;
	}

	public void startCycling() {
		runCycle();
		mSimulationRunning = true;
	}

	public void stopCycling() {
		mCycleHandler.removeMessages(0);
		mSimulationRunning = false;
	}

	// move the selection in the X direction.
	public void moveSelectioninX(float delta) {
		if (Math.abs(delta) > MIN_TRACKBALL_MOVEMENT) {
			if (delta > 0) {
				mSelectedGridX++;
				if (mSelectedGridX >= mXGridCount) {
					mSelectedGridX = 0;
				}

			} else {
				mSelectedGridX--;
				if (mSelectedGridX < 0) {
					mSelectedGridX = mXGridCount - 1;
				}
			}

			// redraw this scene.
			this.invalidate();
		}
	}

	// move the selection in the y direction.
	public void moveSelectioninY(float delta) {
		if (Math.abs(delta) > MIN_TRACKBALL_MOVEMENT) {
			if (delta > 0) {

				mSelectedGridY++;
				if (mSelectedGridY >= mYGridCount) {
					mSelectedGridY = 0;
				}

			} else {

				mSelectedGridY--;
				if (mSelectedGridY < 0) {
					mSelectedGridY = mYGridCount - 1;
				}
			}
			// redraw this scene.
			this.invalidate();

		}
	}

	public void parseTrackball(MotionEvent event) {

		// we don't even move the selection location if the screen is locked.
		if (isScreenLocked() == false) {

			// we'll do this simply. Which direction is bigger... we could try
			// diagnols, but I'm not
			// going to work on that just yet.
			if (Math.abs(event.getY()) > Math.abs(event.getX())) {
				moveSelectioninY(event.getY());
			} else {
				moveSelectioninX(event.getX());
			}

			if (event.getAction() == MotionEvent.ACTION_DOWN) {
				toggleGridIndex(mSelectedGridX, mSelectedGridY);
				// let's redraw the screen.
				this.invalidate();
			}
		}

	}

	private void toggleGridIndex(GridLocation location) {
		toggleGridIndex(location.x, location.y);
	}

	/**
	 * toggleGridIndex: This is a function to change a grid location. This
	 * currently toggles, but it could eventually toggle between 3 indexs.
	 * 
	 * @param x
	 *            the x grid location
	 * @param y
	 *            the y grid location
	 */
	private void toggleGridIndex(int x, int y) {
		// make sure the location is valid and make sure we can toggle an index.
		if (x >= 0 && x < mXGridCount && y >= 0 && y < mYGridCount
				&& isScreenLocked() == false) {
			// find out what the current index is.
			int currentIndex = mGridIndexs[x][y];

			// we're going to toggle the current index for now.
			if (currentIndex != 0) {
				setGridIndex(0, x, y);
			} else {
				setGridIndex(1, x, y);
			}
		}
	}

	@Override
	public void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		for (int x = 0; x < mXGridCount; x += 1) {
			for (int y = 0; y < mYGridCount; y += 1) {

				if (x == mSelectedGridX && y == mSelectedGridY
						&& isScreenLocked() == false) {

					if (mSelectedGrid != null) {
						canvas.drawBitmap(mSelectedGrid, mXGridOffset + x
								* mGridSize, mYGridOffset + y * mGridSize,
								mPaint);
					}

				} else {

					if (mBaseGrid != null) {
						// draw the base grid.
						canvas.drawBitmap(mBaseGrid, mXGridOffset + x
								* mGridSize, mYGridOffset + y * mGridSize,
								mPaint);
					}

				}

				int targetindex = mGridIndexs[x][y];

				if (targetindex > 0) {
					if (targetindex < mNumGridElements
							&& mGridElementArray[targetindex] != null) {
						canvas.drawBitmap(mGridElementArray[targetindex],
								mXGridOffset + x * mGridSize, mYGridOffset + y
										* mGridSize, mPaint);
					}
				}
			}
		}

	}

	/**
	 * Function to set the specified Drawable as the Selected Grid element for
	 * all grid locations.
	 * 
	 * @param gridDrawable
	 */
	public void loadSelectedGrid(Drawable selectedGridDrawable) {
		// load the Bitmap and scale it to a Grid Size
		Bitmap bitmap = Bitmap.createBitmap(mGridSize, mGridSize, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        selectedGridDrawable.setBounds(0, 0, mGridSize, mGridSize);
        selectedGridDrawable.draw(canvas);
        
        mSelectedGrid = bitmap;
	}

	public void gridSizeChanged(int newSize) {
		mGridSize = newSize;

		calculateGridSize();

		resizeAllBitmaps();

	}

	void resizeAllBitmaps() {
		mBaseGrid = scaleBitmap(mBaseGrid, mGridSize, mGridSize);
		mSelectedGrid = scaleBitmap(mSelectedGrid, mGridSize, mGridSize);
		for (int elementIndex = 0; elementIndex < mNumGridElements; elementIndex++) {
			if (mGridElementArray[elementIndex] != null) {
				mGridElementArray[elementIndex] = scaleBitmap(
						mGridElementArray[elementIndex], mGridSize, mGridSize);
			}
		}
	}

	/**
	 * This reads in the grid size from the preferences.
	 */
	public void gridSizeRead() {
		Context currentContext = getContext();
		SharedPreferences sp = PreferenceManager
				.getDefaultSharedPreferences(currentContext);
		String strGridSize = sp.getString(PREF_GRID_SIZE, "24");
		mGridSize = Integer.parseInt(strGridSize.trim());

	}

}
